﻿#region Png Error Checking Recommendations
/*
10.1. Error checking
To ensure early detection of common file-transfer problems, decoders should verify that all eight bytes of the PNG file signature are correct. (See Rationale: PNG file signature.) A decoder can have additional confidence in the file's integrity if the next eight bytes are an IHDR chunk header with the correct chunk length. 
Unknown chunk types must be handled as described in Chunk naming conventions. An unknown chunk type is not to be treated as an error unless it is a critical chunk. 
It is strongly recommended that decoders should verify the CRC on each chunk. 
In some situations it is desirable to check chunk headers (length and type code) before reading the chunk data and CRC. The chunk type can be checked for plausibility by seeing whether all four bytes are ASCII letters (codes 65-90 and 97-122); note that this need be done only for unrecognized type codes. If the total file size is known (from file system information, HTTP protocol, etc), the chunk length can be checked for plausibility as well. 
If CRCs are not checked, dropped/added data bytes or an erroneous chunk length can cause the decoder to get out of step and misinterpret subsequent data as a chunk header. Verifying that the chunk type contains letters is an inexpensive way of providing early error detection in this situation. 
For known-length chunks such as IHDR, decoders should treat an unexpected chunk length as an error. Future extensions to this specification will not add new fields to existing chunks; instead, new chunk types will be added to carry new information. 
Unexpected values in fields of known chunks (for example, an unexpected compression method in the IHDR chunk) must be checked for and treated as errors. However, it is recommended that unexpected field values be treated as fatal errors only in critical chunks. An unexpected value in an ancillary chunk can be handled by ignoring the whole chunk as though it were an unknown chunk type. (This recommendation assumes that the chunk's CRC has been verified. In decoders that do not check CRCs, it is safer to treat any unexpected value as indicating a corrupted file.) 
*/
#endregion

#region Imported Libraries
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using org.webguvenligi.NSecureImageLib.GraphicFileFormats.PNG.Exceptions;
using org.webguvenligi.NSecureImageLib.GraphicFileFormats.Utilities;
#endregion

namespace org.webguvenligi.NSecureImageLib.GraphicFileFormats.PNG
{
    #region Critical Png Chunks
    public class IENDChunk : PNGBaseChunk
    {
        public IENDChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            if (this.TotalChunkSize != 0)
                throw new InvalidDataException("PNG End Chunk Cannot Have A Body");
        }

        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class IHDRChunk : PNGBaseChunk
    {
        //The IHDR chunk must appear FIRST. It contains: 
        //Width:              4 bytes
        //Height:             4 bytes
        //Bit depth:          1 byte
        //Color type:         1 byte
        //Compression method: 1 byte
        //Filter method:      1 byte
        //Interlace method:   1 byte

        /// <summary>
        /// Initializes a new instance of the <see cref="IHDRChunk"/> class.
        /// </summary>
        /// <param name="pngReader">The PNG reader.</param>
        public IHDRChunk(BinaryReader pngReader)
            : base(pngReader)
        {
            base.PngReader = pngReader;
        }

        /// <summary>
        /// Gets or sets the width.
        /// </summary>
        /// <value>The width.</value>
        public int Width
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the height.
        /// </summary>
        /// <value>The height.</value>
        public int Height
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets or sets the bit depth.
        /// </summary>
        /// <value>The bit depth.</value>
        public byte BitDepth
        {
            get;
            private set;
        }
        public int BitsPerPixel
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets or sets the type of the color.
        /// </summary>
        /// <value>The type of the color.</value>
        public ColorEnumType ColorType
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets or sets the compression.
        /// </summary>
        /// <value>The compression.</value>
        public byte Compression
        {
            get
            {
                return _compression;
            }
            private set
            {
                _compression = value;
            }
        }
        private byte _compression;

        /// <summary>
        /// Gets or sets the filter.
        /// </summary>
        /// <value>The filter.</value>
        public byte Filter
        {
            get
            {
                return _filter;
            }
            private set
            {
                _filter = value;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private byte _filter;

        /// <summary>
        /// Gets or sets the interlace.
        /// </summary>
        /// <value>The interlace.</value>
        public byte Interlace
        {
            get
            {
                return _interlace;
            }
            set
            {
                _interlace = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private byte _interlace;

        /// <summary>
        /// </summary>
        /// <param name="chunkDataReader"></param>
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            chunkDataReader.EnsureBytes(4);
            this.Width = chunkDataReader.ReadLittleEndianInt32();
            chunkDataReader.EnsureBytes(4);
            this.Height = chunkDataReader.ReadLittleEndianInt32();
            chunkDataReader.EnsureBytes(1);
            this.BitDepth = chunkDataReader.ReadByte();
            chunkDataReader.EnsureBytes(1);

            byte[] colorTypes =
                new byte[] { 0, 2, 3, 4, 6 };

            byte tempColorType =
                chunkDataReader.ReadByte();

            if (!colorTypes.Contains(tempColorType))
                throw new InvalidDataException("Undefined Color Type Detected");

            this.ColorType =
                (ColorEnumType)tempColorType;

            /*          Color    Allowed    Interpretation
                        Type    Bit Depths   
                        0       1,2,4,8,16  Each pixel is a grayscale sample.  
                        2       8,16        Each pixel is an R,G,B triple.   
                        3       1,2,4,8     Each pixel is a palette index; a PLTE chunk must appear. 
                        4       8,16        Each pixel is a grayscale sample, followed by an alpha sample.   
                        6       8,16        Each pixel is an R,G,B triple, followed by an alpha sample.
            */
            CheckColorTypeAndBitDepthValidity();
            //At present, only compression method 0 (deflate/inflate compression with a sliding window of at most 32768 bytes) is defined.
            CheckFlagData(chunkDataReader, ref _compression, 0);
            ////At present, only filter method 0 (adaptive filtering with five basic filter types) is defined. 
            CheckFlagData(chunkDataReader, ref _filter, 0);
            //Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). 
            CheckFlagData(chunkDataReader, ref _interlace, 0, 1);
            ProcessBitsPerPixel();
        }
        /// <summary>
        /// Gets the filter byte offset.
        /// </summary>
        /// <value>The filter byte offset.</value>
        public int FilterByteOffset
        {
            get
            {
                if (BitDepth < 8) return 1;
                return (BitsPerPixel / 8);
            }
        }
        /// <summary>
        /// Processes the bits per pixel.
        /// </summary>
        private void ProcessBitsPerPixel()
        {
            switch (this.ColorType)
            {
                case ColorEnumType.RGBTribleAlpha:
                    this.BitsPerPixel = 4 * BitDepth;
                    break;
                case ColorEnumType.RGBTrible:
                    this.BitsPerPixel = 3 * BitDepth;
                    break;
                case ColorEnumType.GrayScaleAlpha:
                    this.BitsPerPixel = 2 * BitDepth;
                    break;
                case ColorEnumType.GrayScale:
                case ColorEnumType.PaletteIndex:
                    this.BitsPerPixel = BitDepth;
                    break;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public enum ColorEnumType : byte
        {
            /// <summary>
            /// 
            /// </summary>
            GrayScale = 0,
            /// <summary>
            /// 
            /// </summary>
            RGBTrible = 2,
            /// <summary>
            /// 
            /// </summary>
            PaletteIndex = 3,
            /// <summary>
            /// 
            /// </summary>
            GrayScaleAlpha = 4,
            /// <summary>
            /// 
            /// </summary>
            RGBTribleAlpha = 6
        }
        /// <summary>
        /// Checks the flag data.
        /// </summary>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        /// <param name="dataStore">The data store.</param>
        /// <param name="possibleValues">The possible values.</param>
        private void CheckFlagData(BinaryReader chunkDataReader, ref byte dataStore, params byte[] possibleValues)
        {
            chunkDataReader.EnsureBytes(1);
            dataStore = chunkDataReader.ReadByte();
            bool possibleIndicator = false;
            for (int i = 0; i < possibleValues.Length; i++)
            {
                if (dataStore == possibleValues[i])
                {
                    possibleIndicator = true;
                    break;
                }
            }
            if (!possibleIndicator)
                throw new InvalidDataException();
        }

        /// <summary>
        /// Checks the color type and bit depth validity.
        /// </summary>
        private void CheckColorTypeAndBitDepthValidity()
        {
            bool BitDepthValid = false;
            switch ((byte)ColorType)
            {
                case 0:
                    BitDepthValid = (BitDepth == 1 || BitDepth == 2 || BitDepth == 4 || BitDepth == 8 || BitDepth == 16);
                    break;
                case 2:
                case 4:
                case 6:
                    BitDepthValid = (BitDepth == 8 || BitDepth == 16);
                    break;
                case 3:
                    BitDepthValid = (BitDepth == 1 || BitDepth == 2 || BitDepth == 4 || BitDepth == 8);
                    break;
            }
            if (!BitDepthValid)
                throw new InvalidDataException();
        }

        /// <summary>
        /// Gets the type of the preciding.
        /// </summary>
        /// <value>The type of the preciding.</value>
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class PLTEChunk : PNGBaseChunk
    {
        private List<Color> _usedColors;
        /// <summary>
        /// Gets the used colors.
        /// </summary>
        /// <value>The used colors.</value>
        public ReadOnlyCollection<Color> UsedColors
        {
            get
            {
                return new System.Collections.ObjectModel.ReadOnlyCollection<Color>(_usedColors);
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="PLTEChunk"/> class.
        /// </summary>
        /// <param name="pngReader">The PNG reader.</param>
        public PLTEChunk(BinaryReader pngReader)
            : base(pngReader)
        {
            _usedColors = new List<Color>();
        }

        /// <summary>
        /// </summary>
        /// <param name="chunkDataReader"></param>
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {

            if ((this.TotalChunkSize % 3) != 0)
                throw new InvalidDataException("PLTE Chunk Length Should Be Divideable By 3");

            int paletteSize =
                ((int)this.TotalChunkSize) / 3;

            if (paletteSize > 256)
                throw new PNGBaseException("Palette Size Cannot Be Greater Then 256");

            for (int i = 0; i < paletteSize; i++)
            {
                _usedColors.Add(Color.FromArgb((int)chunkDataReader.ReadByte(),
                                               (int)chunkDataReader.ReadByte(),
                                               (int)chunkDataReader.ReadByte()));
            }

            switch (((IHDRChunk)this.PrecidingChunk).ColorType)
            {
                case IHDRChunk.ColorEnumType.GrayScale:
                case IHDRChunk.ColorEnumType.GrayScaleAlpha:
                    throw new PNGBaseException("Palette Element In Gray Scale Image");
                case IHDRChunk.ColorEnumType.PaletteIndex:
                    int targetPaletteSize = (2 << ((IHDRChunk)this.PrecidingChunk).BitDepth);
                    if (paletteSize > targetPaletteSize)
                        throw new PNGBaseException("Palette Size Cannot Be Greater Then 256");
                    break;
            }
            PaletteSize = paletteSize;
        }
        /// <summary>
        /// Gets or sets the size of the palette.
        /// </summary>
        /// <value>The size of the palette.</value>
        public int PaletteSize
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets the type of the preciding.
        /// </summary>
        /// <value>The type of the preciding.</value>
        protected override Type PrecidingType
        {
            get { return typeof(IHDRChunk); }
        }
    }
    public class IDATChunk : PNGBaseChunk
    {
        public IDATChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
        }
        public byte[] InternalChunkData
        {
            get
            {
                return base.RawChunkData;
            }
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }
    #endregion

    public sealed class ChunkTypeDetail
    {
        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return HelperUtilities.ToString(this);
        }

        /// <summary>
        /// Gets or sets the chunk type indicator.
        /// </summary>
        /// <value>The chunk type indicator.</value>
        public byte[] ChunkTypeIndicator
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets or sets the chunk type description.
        /// </summary>
        /// <value>The chunk type description.</value>
        public string ChunkTypeDescription
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets a value indicating whether this <see cref="ChunkTypeDetail"/> is critical.
        /// </summary>
        /// <value><c>true</c> if critical; otherwise, <c>false</c>.</value>
        public bool Critical
        {
            get
            {
                return !Ancillary;
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="ChunkTypeDetail"/> is ancillary.
        /// </summary>
        /// <value><c>true</c> if ancillary; otherwise, <c>false</c>.</value>
        public bool Ancillary
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets a value indicating whether this <see cref="ChunkTypeDetail"/> is public.
        /// </summary>
        /// <value><c>true</c> if public; otherwise, <c>false</c>.</value>
        public bool Public
        {
            get
            {
                return !Private;
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="ChunkTypeDetail"/> is private.
        /// </summary>
        /// <value><c>true</c> if private; otherwise, <c>false</c>.</value>
        public bool Private
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether [safe to copy].
        /// </summary>
        /// <value><c>true</c> if [safe to copy]; otherwise, <c>false</c>.</value>
        public bool SafeToCopy
        {
            get;
            private set;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ChunkTypeDetail"/> class.
        /// </summary>
        /// <param name="chunkTypeIndicator">The chunk type indicator.</param>
        public ChunkTypeDetail(string chunkTypeIndicator)
        {
            if (String.IsNullOrEmpty(chunkTypeIndicator))
                throw new InvalidDataException("Chunk Type Is Invalid.");
            else
                ParseChunkType(Encoding.ASCII.GetBytes(chunkTypeIndicator));
        }

        /// <summary>
        /// Parses the type of the chunk.
        /// </summary>
        /// <param name="chunkTypeIndicator">The chunk type indicator.</param>
        private void ParseChunkType(byte[] chunkTypeIndicator)
        {
            if (chunkTypeIndicator != null &&
                chunkTypeIndicator.Length == 4 &&
                chunkTypeIndicator.IsAscii())
            {
                this.ChunkTypeIndicator = chunkTypeIndicator;
                this.ChunkTypeDescription =
                       Encoding.ASCII.GetString(ChunkTypeIndicator);
                this.Ancillary = chunkTypeIndicator[0].GetBit(5);
                this.Private = chunkTypeIndicator[1].GetBit(5);
                this.SafeToCopy = chunkTypeIndicator[3].GetBit(5);
            }
            else
                throw new InvalidDataException("Chunk Type Is Invalid.");
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ChunkTypeDetail"/> class.
        /// </summary>
        /// <param name="chunkTypeIndicator">The chunk type indicator.</param>
        public ChunkTypeDetail(byte[] chunkTypeIndicator)
        {
            ParseChunkType(chunkTypeIndicator);
        }
    }

    #region Ancillary Png Chunks

    #region Miscellaneous information

    public class HistogramChunk : PNGAncillaryChunkBase
    {
        public HistogramChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        public System.Collections.ObjectModel.ReadOnlyCollection<UInt16> HistogramValues
        {
            get
            {
                return new System.Collections.ObjectModel.ReadOnlyCollection<ushort>(_histoChunk);
            }
        }
        private List<UInt16> _histoChunk = new List<UInt16>();

        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);

            if (this.PrecidingChunk == null)
                throw new Exceptions.PNGBaseException("Missing PLTE Chunk Before Histogram Chunk");

            if (TotalChunkSize != ((this.PrecidingChunk as PLTEChunk).UsedColors.Count * 2))
            {
                throw new InvalidDataException("Histogram must match Palette Color Count");
            }
            for (int i = 0; i < TotalChunkSize; i += 2)
            {
                _histoChunk.Add(chunkDataReader.ReadLittleEndianUInt16());
            }
        }
        protected override Type PrecidingType
        {
            get { return typeof(PLTEChunk); }
        }
    }

    public class TimeChunk : PNGAncillaryChunkBase
    {
        private DateTime _creationDate;

        public DateTime CreationDate
        {
            get
            {
                return _creationDate;
            }
            set
            {
                byte[] year = BitConverter.GetBytes((ushort)value.Year);
                this.RawChunkData = new byte[8];
                this.RawChunkData[0] = year[0];
                this.RawChunkData[1] = year[1];
                this.RawChunkData[2] = (byte)value.Month;
                this.RawChunkData[3] = (byte)value.Day;
                this.RawChunkData[4] = (byte)value.Hour;
                this.RawChunkData[6] = (byte)value.Minute;
                this.RawChunkData[7] = (byte)value.Second;
                _creationDate = value;
            }
        }

        public TimeChunk(DateTime creationDate)
            : base(null)
        {
            this.CreationDate = creationDate;
        }

        public TimeChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
        protected override int ChunkSize
        {
            get
            {
                return 7;
            }
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            byte[] year = new byte[2];
            year = chunkDataReader.ReadBytes(2).Reverse().ToArray();
            byte month = chunkDataReader.ReadByte();
            byte day = chunkDataReader.ReadByte();
            byte hour = chunkDataReader.ReadByte();
            byte minute = chunkDataReader.ReadByte();
            byte second = chunkDataReader.ReadByte();
            CreationDate = new DateTime(BitConverter.ToInt16(year, 0), month, day, hour, minute, second);
        }
    }

    /// <summary>
    /// 4.2.4.2. pHYs Physical pixel dimensions
    /// </summary>
    /// <remarks>
    /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains: 
    ///   Pixels per unit, X axis: 4 bytes (unsigned integer)
    ///   Pixels per unit, Y axis: 4 bytes (unsigned integer)
    ///   Unit specifier:          1 byte
    ///  The following values are defined for the unit specifier: 
    ///   0: unit is unknown
    ///   1: unit is the meter
    /// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. 
    /// Conversion note: one inch is equal to exactly 0.0254 meters. 
    /// If this ancillary chunk is not present, pixels are assumed to be square, and the physical size of each pixel is unknown. 
    /// If present, this chunk must precede the first IDAT chunk. 
    /// See Recommendations for Decoders: Pixel dimensions. 
    /// </remarks>
    public class PhysChunk : PNGAncillaryChunkBase
    {
        public enum enUnitSpecifier : byte
        {
            Unknown = 0,
            Meter = 1
        }
        public PhysChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        public uint PPUXAxis
        {
            get;
            private set;
        }
        public uint PPUYAxis
        {
            get;
            private set;
        }
        public enUnitSpecifier UnitSpecifier
        {
            get;
            private set;
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
        //Pixels per unit, X axis: 4 bytes (unsigned integer)
        //Pixels per unit, Y axis: 4 bytes (unsigned integer)
        //Unit specifier:          1 byte
        protected override int ChunkSize
        {
            get
            {
                return 9;
            }
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            this.PPUXAxis = chunkDataReader.ReadLittleEndianUInt32();
            this.PPUYAxis = chunkDataReader.ReadLittleEndianUInt32();
            byte unitSpecification = chunkDataReader.ReadByte();
            if (unitSpecification != 0 && unitSpecification != 1)
                throw new InvalidDataException("Unit Specifier Should Be 0 or 1");
            this.UnitSpecifier = (enUnitSpecifier)unitSpecification;
        }
    }

    public class PCalChunk : PNGAncillaryChunkBase
    {
        public PCalChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        public int OriginalZero
        {
            get;
            private set;
        }
        public int OriginalMax
        {
            get;
            private set;
        }
        public byte EquationType
        {
            get;
            private set;
        }
        public byte NumberOfParameters
        {
            get;
            private set;
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            string calibrationName =
                chunkDataReader.ReadToNull(79);
            chunkDataReader.EnsureBytes(4);
            OriginalZero = chunkDataReader.ReadLittleEndianInt32();
            chunkDataReader.EnsureBytes(4);
            OriginalMax = chunkDataReader.ReadLittleEndianInt32();
            chunkDataReader.EnsureBytes(1);
            EquationType = chunkDataReader.ReadByte();
            chunkDataReader.EnsureBytes(1);
            NumberOfParameters = chunkDataReader.ReadByte();
            byte[] remainingBytes =
                chunkDataReader.ReadBytes();
            int nullSeparatorCount = (from remainingByte in remainingBytes
                                      where (remainingByte == 0)
                                      select remainingByte).Count();
            if (nullSeparatorCount != NumberOfParameters)
                throw new NonFatalPNGException("Invalid PCalChunk Detected");
            BinaryReader tempReader = new BinaryReader(new MemoryStream(remainingBytes));
            tempReader.BaseStream.Position = 0;
            UnitName = tempReader.ReadNullDelimitedString();
            for (int i = 0; i < NumberOfParameters - 1; i++)
            {
            }
        }
        public string UnitName
        {
            get;
            private set;
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    public class OffChunk : PNGAncillaryChunkBase
    {
        public OffChunk(BinaryReader pngReader)
            : base(pngReader)
        {

        }
        public Point OffPoint
        {
            get;
            private set;
        }
        public byte OffType
        {
            get;
            private set;
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            int x = chunkDataReader.ReadLittleEndianInt32();
            int y = chunkDataReader.ReadLittleEndianInt32();
            byte type = chunkDataReader.ReadByte();
            if (type != 0 || type != 1)
                throw new NonFatalPNGException("Undefined OffType");
            this.OffType = type;
            this.OffPoint = new Point(x, y);
        }
        protected override int ChunkSize
        {
            get
            {
                return 9;
            }
        }

        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    public class ScalChunk : PNGAncillaryChunkBase
    {
        public enum enScale : byte
        {
            Meter = 1,
            Radian = 2
        }
        public ScalChunk(BinaryReader pngReader)
            : base(pngReader)
        {

        }
        public enScale Scale
        {
            get;
            private set;
        }
        public double Width
        {
            get;
            private set;
        }
        public double Height
        {
            get;
            private set;
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            chunkDataReader.EnsureBytes(1);
            byte scalType = chunkDataReader.ReadByte();
            this.Scale = (enScale)scalType;
            if (scalType != 0 || scalType != 1)
                throw new NonFatalPNGException();
            double width = chunkDataReader.ReadFloatingPoint();
            double height = chunkDataReader.ReadFloatingPoint();
            if (width <= 0 || height <= 0)
                throw new NonFatalPNGException();
            this.Width = width;
            this.Height = height;
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    #endregion

    #region ColorSpaceInformation

    public class BKGDChunk : PNGAncillaryChunkBase
    {

        private List<int> _backgroundIndexes =
            new List<int>();

        public System.Collections.ObjectModel.ReadOnlyCollection<int> BackgroundIndexes
        {
            get
            {
                return
                    new System.Collections.ObjectModel.ReadOnlyCollection<int>(_backgroundIndexes);
            }
        }

        public BKGDChunk(BinaryReader pngReader)
            : base(pngReader)
        {

        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            switch (((IHDRChunk)this.PrecidingChunk).ColorType)
            {
                case IHDRChunk.ColorEnumType.PaletteIndex:
                    chunkDataReader.CheckTotalLength(1);
                    _backgroundIndexes.Add(chunkDataReader.ReadByte());
                    break;
                case IHDRChunk.ColorEnumType.GrayScale:
                case IHDRChunk.ColorEnumType.GrayScaleAlpha:
                    chunkDataReader.CheckTotalLength(2);
                    //TODO Check Here For Correctless
                    _backgroundIndexes.Add(chunkDataReader.ReadLittleEndianInt16());
                    break;
                default:
                    chunkDataReader.CheckTotalLength(6);
                    //TODO Check Her For Correctless
                    _backgroundIndexes.AddRange(new int[] { chunkDataReader.ReadLittleEndianInt16(),
                                                            chunkDataReader.ReadLittleEndianInt16(),
                                                            chunkDataReader.ReadLittleEndianInt16()}.ToList());
                    break;
            }
        }

        protected override Type PrecidingType
        {
            get { return typeof(IHDRChunk); }
        }
    }

    /// <summary>
    /// 4.2.2.1. gAMA Image gamma
    /// </summary>
    /// <remarks> The gAMA chunk specifies the relationship between the image samples and the desired display output intensity as a power function: 
    /// sample = light_out ^ gamma
    /// Here sample and light_out are normalized to the range 0.0 (minimum intensity) to 1.0 (maximum intensity). Therefore: 
    /// sample = integer_sample / (2^bitdepth - 1)
    /// The gAMA chunk contains: 
    /// Gamma: 4 bytes
    /// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. For example, a gamma of 1/2.2 would be stored as 45455. 
    /// The gamma value has no effect on alpha samples, which are always a linear fraction of full opacity. 
    /// If the encoder does not know the image's gamma value, it should not write a gAMA chunk; the absence of a gAMA chunk indicates that the gamma is unknown. 
    /// Technically, "desired display output intensity" is not specific enough; one needs to specify the viewing conditions under which the output is desired. For gAMA these are the reference viewing conditions of the sRGB specification [sRGB], which are based on ISO standards [ISO-3664]. Adjusting for different viewing conditions is a complex process normally handled by a Color Management System (CMS). If this adjustment is not performed, the error is usually small. Applications desiring high color fidelity may wish to use an sRGB chunk (see the sRGB chunk specification) or an iCCP chunk (see the iCCP chunk specification). 
    /// If the gAMA chunk appears, it must precede the first IDAT chunk, and it must also precede the PLTE chunk if present. An sRGB chunk or iCCP chunk, when present and recognized, overrides the gAMA chunk. 
    /// See Gamma correction, Recommendations for Encoders: Encoder gamma handling, and Recommendations for Decoders: Decoder gamma handling. 
    /// </remarks>
    public class GamaChunk : PNGAncillaryChunkBase
    {
        /// <summary>
        /// The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. For example, a gamma of 1/2.2 would be stored as 45455. 
        /// </summary>
        public uint GamaValue
        {
            get;
            private set;
        }
        public float GamaCalculatedValue
        {
            get
            {
                return ((float)GamaValue / (float)100000);
            }
        }
        public GamaChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override int ChunkSize
        {
            get
            {
                return 4;
            }
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            this.GamaValue = chunkDataReader.ReadLittleEndianUInt32();
            if (this.GamaValue == 0)
                throw new NonFatalPNGException("Invalid Gama Value");
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    public class SBitChunk : PNGAncillaryChunkBase
    {
        public SBitChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }

        private List<byte> _sbitArray = new List<byte>();

        public System.Collections.ObjectModel.ReadOnlyCollection<byte> Sbits
        {
            get
            {
                return
                    new System.Collections.ObjectModel.ReadOnlyCollection<byte>(_sbitArray);
            }
        }

        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);

            IHDRChunk tempChunk =
                (IHDRChunk)this.PrecidingChunk;

            int remainingBytes = 0;

            int depth = tempChunk.BitDepth;

            switch (tempChunk.ColorType)
            {
                case IHDRChunk.ColorEnumType.GrayScale:
                    remainingBytes = 1;
                    break;
                case IHDRChunk.ColorEnumType.RGBTrible:
                case IHDRChunk.ColorEnumType.PaletteIndex:
                    depth = 8;
                    remainingBytes = 3;
                    break;
                case IHDRChunk.ColorEnumType.GrayScaleAlpha:
                    remainingBytes = 2;
                    break;
                case IHDRChunk.ColorEnumType.RGBTribleAlpha:
                    remainingBytes = 4;
                    break;
            }

            chunkDataReader.CheckTotalLength(remainingBytes);

            for (int i = 0; i < remainingBytes; i++)
            {
                byte currentByte =
                    chunkDataReader.ReadByte();
                if (currentByte <= 0 || currentByte > depth)
                    throw new PNG.Exceptions.PNGBaseException("Illegal sBIT sample depth");
            }
        }
        protected override Type PrecidingType
        {
            get { return typeof(IHDRChunk); }
        }
    }

    public class SPLTChunk : PNGTextChunkBase
    {
        public class SPLTChildItem
        {

        }
        public class SPLTChildItem8 : SPLTChildItem
        {
            public Color ARGBColor
            {
                get;
                set;
            }
            public SPLTChildItem8()
            {
            }
            public Int16 Frequency
            {
                get;
                set;
            }
        }
        public class SPLTChildItem16 : SPLTChildItem
        {

        }

        //TODO Make this readonly
        public List<SPLTChildItem> SPLTChildItems =
            new List<SPLTChildItem>();

        public SPLTChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        public byte SampleDepth
        {
            get;
            private set;
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            if (PrecidingChunk != null)
                throw new InvalidDataException("SPLT After IDat");
            chunkDataReader.EnsureBytes(1);
            byte sampleDepth = chunkDataReader.ReadByte();
            if (sampleDepth != 8 && sampleDepth != 16)
                throw new NonFatalPNGException("Parsing Of SPLT Chunk Failed. Sample Depth Should Be 8 or 16", new InvalidDataException("Parsing Of SPLT Chunk Failed. Sample Depth Should Be 8 or 16"));

            this.SampleDepth =
                sampleDepth;

            if (sampleDepth == 8)
            {
                if ((chunkDataReader.BytesRemaining() % 6) != 0)
                    throw new NonFatalPNGException("", new InvalidDataException("SPLT Entries Not Valid. Used Mod Was 6."));
            }
            else
            {
                if ((chunkDataReader.BytesRemaining() % 10) != 0)
                    throw new NonFatalPNGException("", new InvalidDataException("SPLT Entries Not Valid. Used Mod Was 10."));
            }
            while (chunkDataReader.BytesRemaining() > 0)
            {
                //TODO Correct Here
                SPLTChildItem tempItem = null;
                if (sampleDepth == 8)
                {
                    tempItem = new SPLTChildItem8();
                    ((SPLTChildItem8)tempItem).ARGBColor = Color.FromArgb(0, 0, 0, 0);
                    chunkDataReader.ReadBytes(6);
                }
                else
                {
                    tempItem = new SPLTChildItem16();
                    chunkDataReader.ReadBytes(10);
                }
                SPLTChildItems.Add(tempItem);
            }
        }
        protected override Type PrecidingType
        {
            get { return typeof(IDATChunk); }
        }
    }

    /// <summary>
    /// 4.2.2.3. sRGB Standard RGB color space
    /// </summary>
    public class SRGBChunk : PNGAncillaryChunkBase
    {
        public enum enRenderingIntent : byte
        {
            Perceptual = 0,
            RelativeColorimetric = 1,
            Saturation = 2,
            AbsoluteColorimetric = 3
        }
        public SRGBChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override int ChunkSize
        {
            get
            {
                return 1;
            }
        }
        public enRenderingIntent RenderingIntent
        {
            get;
            private set;
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            byte SRGBContent = chunkDataReader.ReadByte();
            byte[] possibleValues = (byte[])Enum.GetValues(typeof(enRenderingIntent));
            if (!possibleValues.Contains(SRGBContent))
                throw new InvalidDataException("Rendering Intent Is Invalid");
            this.RenderingIntent = (enRenderingIntent)SRGBContent;
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    /// <summary>
    /// 4.2.2.4. iCCP Embedded ICC profile
    /// </summary>
    /// <remarks>
    /// If the iCCP chunk is present, the image samples conform to the color space represented by the embedded ICC profile as defined by the International Color Consortium [ICC]. The color space of the ICC profile must be an RGB color space for color images (PNG color types 2, 3, and 6), or a monochrome grayscale color space for grayscale images (PNG color types 0 and 4). 
    /// The iCCP chunk contains: 
    ///   Profile name:       1-79 bytes (character string)
    ///   Null separator:     1 byte
    ///   Compression method: 1 byte
    ///   Compressed profile: n bytes
    /// The format is like the zTXt chunk. (see the zTXt chunk specification). The profile name can be any convenient name for referring to the profile. It is case-sensitive and subject to the same restrictions as the keyword in a text chunk: it must contain only printable Latin-1 [ISO/IEC-8859-1] characters (33-126 and 161-255) and spaces (32), but no leading, trailing, or consecutive spaces. The only value presently defined for the compression method byte is 0, meaning zlib datastream with deflate compression (see Deflate/Inflate Compression). Decompression of the remainder of the chunk yields the ICC profile. 
    /// An application that writes the iCCP chunk should also write gAMA and cHRM chunks that approximate the ICC profile's transfer function, for compatibility with applications that do not use the iCCP chunk. 
    /// When the iCCP chunk is present, applications that recognize it and are capable of color management [ICC] should ignore the gAMA and cHRM chunks and use the iCCP chunk instead, but applications incapable of full-fledged color management should use the gAMA and cHRM chunks if present. 
    /// A file should contain at most one embedded profile, whether explicit like iCCP or implicit like sRGB. 
    /// If the iCCP chunk appears, it must precede the first IDAT chunk, and it must also precede the PLTE chunk if present. 
    /// </remarks>
    public class ICCPChunk : ZTextualChunk
    {
        public ICCPChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
        }
    }



    public class CHRMChunk : PNGAncillaryChunkBase
    {
        public CHRMChunk(BinaryReader pngReader)
            : base(pngReader)
        {

        }
        protected override int ChunkSize
        {
            get
            {
                return 32;
            }
        }
        public ReadOnlyCollection<float> CHRMValues
        {
            get
            {
                return new ReadOnlyCollection<float>(_chrmValues);
            }
        }
        List<float> _chrmValues =
            new List<float>(8);

        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            for (int i = 0; i < 8; i++)
            {
                _chrmValues.Add((((float)chunkDataReader.ReadLittleEndianInt32()) / 100000f));
            }
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    #endregion

    #endregion

    #region Base Classes For Chunks
    public abstract class PNGAncillaryChunkBase : PNGBaseChunk
    {
        public PNGAncillaryChunkBase()
            : base()
        {
        }
        public PNGAncillaryChunkBase(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            if (!this.ChunkTypeValue.Ancillary)
                throw new InvalidDataException("Not an ancillary chunk type");
        }
    }

    public class PNGCriticalChunkBase : PNGBaseChunk
    {
        public PNGCriticalChunkBase(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            if (!this.ChunkTypeValue.Ancillary)
                throw new InvalidDataException("Not an ancillary chunk type");
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }

    public class UnknownAnchillaryChunk : PNGAncillaryChunkBase
    {
        public UnknownAnchillaryChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
    }
    public class UnknownChunk : PNGCriticalChunkBase
    {
        public UnknownChunk(BinaryReader pngReader)
            : base(pngReader)
        {
        }

        protected override Type PrecidingType
        {
            get { return null; }
        }
    }
    #endregion
}
